package com.google.qam.view;

import com.google.qam.R;
import com.google.qam.app.App;
import com.google.qam.texture.ResourceTexture;
import com.google.qam.texture.StringTexture;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public final class MenuBar extends View implements PopupMenu.Listener {
	public static final int HEIGHT = 45;
	
	public static final StringTexture.Config MENU_TITLE_STYLE_TEXT = new StringTexture.Config();
    private static final StringTexture.Config MENU_TITLE_STYLE = new StringTexture.Config();
    private static final int MENU_HIGHLIGHT_EDGE_WIDTH = 21;
    private static final int MENU_HIGHLIGHT_EDGE_INSET = 9;
    private static final int HIT_TEST_MARGIN = 15;
    
    private static final int TOP = 0;
    private static final int BOTTOM = 1;
    
    static {
        MENU_TITLE_STYLE.fontSize = 17 * App.PIXEL_DENSITY;
        MENU_TITLE_STYLE.sizeMode = StringTexture.Config.SIZE_EXACT;
        MENU_TITLE_STYLE.overflowMode = StringTexture.Config.OVERFLOW_FADE;

        MENU_TITLE_STYLE_TEXT.fontSize = 15 * App.PIXEL_DENSITY;
        MENU_TITLE_STYLE_TEXT.xalignment = StringTexture.Config.ALIGN_HCENTER;
        MENU_TITLE_STYLE_TEXT.sizeMode = StringTexture.Config.SIZE_EXACT;
        MENU_TITLE_STYLE_TEXT.overflowMode = StringTexture.Config.OVERFLOW_FADE;
    }
    
    private final Context mContext;
    private final PopupMenu mSubmenu;
    private final Bitmap mBackground;
    private final int mOrientation;
    private final int mPopupType;
    private final int mPopupAnim;
    
    private Menu[] mMenus = {};
    private float mX;
    private float mY;
    private float mWidth;
    private float mHeight;
    private int mTouchMenu = -1;
    private int mTouchMenuItem = -1;
    private boolean mNeedsLayout = false;
    private boolean mTouchActive = false;
    private boolean mTouchOverMenu = false;
    private boolean mSecondTouch;
    private static final int BACKGROUND_TOP = R.drawable.menu_bg_top;
    private static final int BACKGROUND_BOTTOM = R.drawable.menu_bg_bottom;
    private static final int SEPERATOR = R.drawable.menu_divider;
    private static final int MENU_HIGHLIGHT_LEFT = R.drawable.menu_bg_pressed_left;
    private static final int MENU_HIGHLIGHT_MIDDLE = R.drawable.menu_bg_pressed;
    private static final int MENU_HIGHLIGHT_RIGHT = R.drawable.menu_bg_pressed_right;
    
	public MenuBar(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.qam);
		try {
			mOrientation = a.getInteger(R.styleable.qam_orientation, 0);
			mPopupType = a.getInteger(R.styleable.qam_popup_type, 0);
			mPopupAnim = a.getInteger(R.styleable.qam_popup_anim, 0);
		} finally {
			a.recycle();
		}
		
		int bg = mOrientation == TOP ? BACKGROUND_TOP : BACKGROUND_BOTTOM;
		mBackground = ResourceTexture.createResourceTexture(bg).load(mContext);
		
		mSubmenu = new PopupMenu.Builder(context).type(mPopupType).animation(mPopupAnim).build();
		mSubmenu.setListener(this);
	}
	
	public Menu[] getMenus() {
        return mMenus;
    }

    public void setMenus(Menu[] menus) {
        mMenus = menus;
        mNeedsLayout = true;
    }

    public void updateMenu(Menu menu, int index) {
        mMenus[index] = menu;
        mNeedsLayout = true;
    }
    
    public void onDestroy() {
    	if(mSubmenu != null)
    		mSubmenu.close();
    }
    
    public void onSelectionChanged(PopupMenu menu, int hit) {
		mTouchMenuItem = hit;
		invalidate();
	}

	public void onSelectionClicked(PopupMenu menu, int hit) {
		selectMenu(-1);
		invalidate();
	}
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    	if(changed) {
    		if(mOrientation == BOTTOM) {
    			mX = 0f;
        		mY = bottom - HEIGHT * App.PIXEL_DENSITY;
        		mWidth = right;
        		mHeight = bottom;
    		} else if(mOrientation == TOP) {
    			mX = 0f;
        		mY = 0f;
        		mWidth = right;
        		mHeight = HEIGHT * App.PIXEL_DENSITY;
    		}
    	}
    }
	
	@Override
	protected void onDraw(Canvas canvas) {
		if (mNeedsLayout) {
            layoutMenus();
            mNeedsLayout = false;
        }
		
		// Draw the background.
		int backgroundHeight = mBackground.getHeight();
		int menuHeight = (int) (HEIGHT * App.PIXEL_DENSITY + 0.5f);
        int extra = backgroundHeight - menuHeight;
        if(mOrientation == TOP) {
        	drawBitmap2D(canvas, mBackground, mX, mY, mWidth, mHeight + extra);
        } else {
        	drawBitmap2D(canvas, mBackground, mX, mY - extra, mWidth, mHeight);
        }
		
		// Draw the separators.
		Menu[] menus = mMenus;
		int numMenus = menus.length;
		int y = (int) mY;
		Bitmap seperator = ResourceTexture.createResourceTexture(SEPERATOR).load(mContext);
		for (int i = 1; i < numMenus; ++i) {
			int x = menus[i].x;
			drawBitmap2D(canvas, seperator, x, y, x + 1, mHeight);
        }
		
		// Draw the selection / focus highlight.
        int touchMenu = mTouchMenu;
        if (canDrawHighlight()) {
            drawHighlight(canvas, touchMenu);
        }
		
		// Draw labels.
		for (int i = 0; i != numMenus; ++i) {
            // Draw the icon and title.
            Menu menu = menus[i];
            Bitmap icon = ResourceTexture.createResourceTexture(menu.icon).load(mContext);
            Bitmap title = StringTexture.createStringTexture(menu.title, menu.config, 
            		menu.titleWidth, MENU_TITLE_STYLE.height).load(mContext);
            
            int iconWidth = icon != null ? icon.getWidth() : 0;
            int width = iconWidth + menu.titleWidth;
            int offset = (menu.mWidth - width) / 2;
            if (icon != null) {
                float iconY = y + (menuHeight - icon.getHeight()) / 2;
                drawBitmap2D(canvas, icon, menu.x + offset, iconY);
            }
            float titleY = y + (menuHeight - MENU_TITLE_STYLE.height) / 2 + 1;
            drawBitmap2D(canvas, title, menu.x + offset + iconWidth, titleY);
        }
		
		super.onDraw(canvas);
	}
	
	private void drawHighlight(Canvas canvas, int touchMenu) {
        Bitmap highlightLeft = ResourceTexture.createResourceTexture(MENU_HIGHLIGHT_LEFT).load(mContext);
        Bitmap highlightMiddle = ResourceTexture.createResourceTexture(MENU_HIGHLIGHT_MIDDLE).load(mContext);
        Bitmap highlightRight = ResourceTexture.createResourceTexture(MENU_HIGHLIGHT_RIGHT).load(mContext);

        int height = highlightLeft.getHeight();
        int extra = height - (int) (HEIGHT * App.PIXEL_DENSITY);
        Menu menu = mMenus[touchMenu];
        int x = menu.x + (int) (MENU_HIGHLIGHT_EDGE_INSET * App.PIXEL_DENSITY);
        int width = menu.mWidth - (int) ((MENU_HIGHLIGHT_EDGE_INSET * 2) * App.PIXEL_DENSITY);
        int y = (int) mY - extra;

        // Draw left edge.
        drawBitmap2D(canvas, highlightLeft, x - MENU_HIGHLIGHT_EDGE_WIDTH * App.PIXEL_DENSITY, y, x, mHeight);

        // Draw middle.
        drawBitmap2D(canvas, highlightMiddle, x, y, x + width, mHeight);

        // Draw right edge.
        drawBitmap2D(canvas, highlightRight, x + width, y, x + width + MENU_HIGHLIGHT_EDGE_WIDTH * App.PIXEL_DENSITY, mHeight);
    }
	
	private void drawBitmap2D(Canvas canvas, Bitmap bitmap, float x, float y, float width, float height) {
		canvas.drawBitmap(bitmap, null, new RectF(x, y, width, height), null);
		canvas.save();
	}
	
	private void drawBitmap2D(Canvas canvas, Bitmap bitmap, float x, float y) {
		canvas.drawBitmap(bitmap, x, y, null);
		canvas.save();
	}
	
	private int hitTestMenu(int x, int y) {
		if((mOrientation == TOP && y < mHeight + HIT_TEST_MARGIN * App.PIXEL_DENSITY) 
				|| (mOrientation == BOTTOM && y > mY - HIT_TEST_MARGIN * App.PIXEL_DENSITY)) {
			Menu[] menus = mMenus;
            for (int i = menus.length - 1; i >= 0; --i) {
                if (x > menus[i].x) {
                    if (menus[i].onSelect != null || menus[i].options != null || menus[i].onSingleTapUp != null) {
                        return i;
                    } else {
                        return -1;
                    }
                }
            }
		}
        return -1;
    }
	
	private void selectMenu(int index) {
        int oldIndex = mTouchMenu;
        if (oldIndex != index) {
            // Notify on deselect.
            Menu[] menus = mMenus;
            if (oldIndex != -1) {
                Menu oldMenu = menus[oldIndex];
                if (oldMenu.onDeselect != null) {
                    oldMenu.onDeselect.run();
                }
            }

            // Select the new menu.
            mTouchMenu = index;
            mTouchMenuItem = -1;
            
            // Show the submenu for the selected menu if one is provided.
            PopupMenu submenu = mSubmenu;
            boolean didShow = false;
            if (index != -1) {
                // Notify on select.
                Menu menu = mMenus[index];
                if (menu.onSelect != null) {
                    menu.onSelect.run();
                }
                
                // Show the popup menu if options are provided.
                PopupMenu.Option[] options = menu.options;
                if (options != null) {
                	int x = (int) (mX + menu.x);
                	int y = (int) mY;
                	int width = menu.mWidth;
                	int height = (int) (mHeight - mY);
                	didShow = true;
                	submenu.setOptions(options);
                	mSubmenu.showAtPoint(this, x, y, width, height);
                }
            }
            if (!didShow) {
                submenu.close();
            }
        }
    }

	@Override
	public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int hit = hitTestMenu(x, y);
        
        int action = event.getAction();
        switch (action)
        {
        	case MotionEvent.ACTION_DOWN:
	            mTouchActive = true;
	            if (mTouchMenu == hit) {
	                mSecondTouch = true;
	            } else {
	                mSecondTouch = false;
	            }
        
        	case MotionEvent.ACTION_MOVE:
	            // Determine which menu the touch is over.
	            if (hit != -1) {
	                // Select the menu and invoke the action.
	                selectMenu(hit);
	                mTouchOverMenu = true;
	            } else {
	                // Forward events outside the menubar to the active popup menu.
	                mTouchOverMenu = false;
	            }
	            break;
        
        	case MotionEvent.ACTION_UP:
	            if (mTouchMenu == hit && mSecondTouch) {
	            	mSubmenu.close();
	                mTouchMenu = -1;
	                break;
	            }
	
	            // Close the submenu if touch outside
	            if (hit != -1) {
	                // Notify on single tap.
	                Menu menu = mMenus[hit];
	                if (menu.onSingleTapUp != null) {
	                    menu.onSingleTapUp.run();
	                }
	                if (menu.options == null)
	                    selectMenu(-1);
	            } else {
	                selectMenu(-1);
	            }
	            break;

	        case MotionEvent.ACTION_CANCEL:
	            // Always deselect if canceled.
	            selectMenu(-1);
	            break;
        }
        invalidate();
        return true;
	}
	
	private boolean canDrawHighlight() {
        return mTouchMenu != -1 && mTouchMenuItem == -1 && (!mTouchActive || mTouchOverMenu);
    }

	private void layoutMenus() {
        Menu[] menus = mMenus;
        int numMenus = menus.length;
        // we do the best attempt to fit the menu items and resize them
        // also, it tries to minimize different sized menu items
        // it finds the maximum width for a set of menu items, and checks
        // whether that width
        // can be used for all the cells, else, it goes to the next maximum
        // width, so on and
        // so forth
        if (numMenus != 0) {
            float viewWidth = mWidth;
            int occupiedWidth = 0;
            int previousMaxWidth = Integer.MAX_VALUE;
            int totalDesiredWidth = 0;

            for (int i = 0; i < numMenus; i++) {
                totalDesiredWidth += menus[i].computeRequiredWidth();
            }

            if (totalDesiredWidth > viewWidth) {
                // Just split the menus up by available size / nr of menus.
                int widthPerMenu = (int) Math.floor(viewWidth / numMenus);
                int x = 0;

                for (int i = 0; i < numMenus; i++) {
                    Menu menu = menus[i];
                    menu.x = x;
                    menu.mWidth = widthPerMenu;
                    menu.titleWidth = widthPerMenu - (20 + (menu.icon != 0 ? 45 : 0));

                    // fix up rounding errors by adding the last pixel to the
                    // last menu.
                    if (i == numMenus - 1) {
                        menu.mWidth = (int) viewWidth - x;
                    }
                    x += widthPerMenu;

                }
            } else {
                boolean foundANewMaxWidth = true;
                int menusProcessed = 0;

                while (foundANewMaxWidth && menusProcessed < numMenus) {
                    foundANewMaxWidth = false;
                    int maxWidth = 0;
                    for (int i = 0; i < numMenus; ++i) {
                        int width = menus[i].computeRequiredWidth();
                        if (width > maxWidth && width < previousMaxWidth) {
                            foundANewMaxWidth = true;
                            maxWidth = width;
                        }
                    }
                    // can all the menus have this width
                    int cumulativeWidth = maxWidth * (numMenus - menusProcessed) + occupiedWidth;
                    if (cumulativeWidth < viewWidth || !foundANewMaxWidth || menusProcessed == numMenus - 1) {
                        float delta = (viewWidth - cumulativeWidth) / numMenus;
                        if (delta < 0) {
                            delta = 0;
                        }
                        int x = 0;
                        for (int i = 0; i < numMenus; ++i) {
                            Menu menu = menus[i];
                            menu.x = x;
                            float width = menus[i].computeRequiredWidth();
                            if (width < maxWidth) {
                                width = maxWidth + delta;
                            } else {
                                width += delta;
                            }
                            menu.mWidth = (int) width;
                            menu.titleWidth = StringTexture.computeTextWidthForConfig(menu.title, menu.config);
                            x += width;
                        }
                        break;
                    } else {
                        ++menusProcessed;
                        previousMaxWidth = maxWidth;
                        occupiedWidth += maxWidth;
                    }
                }
            }
        }
    }
	
	public static final class Menu {
        public final String title;
        public int titleWidth = 0;
        public final StringTexture.Config config;
        public final int icon;
        public final Runnable onSelect;
        public final Runnable onDeselect;
        public final Runnable onSingleTapUp;
        public final boolean resizeToAccomodate;
        public PopupMenu.Option[] options;
        private int x;
        private int mWidth;
        private static final float ICON_WIDTH = 45.0f;

        public static final class Builder {
            private final String title;
            private StringTexture.Config config;
            private int icon = 0;
            private Runnable onSelect = null;
            private Runnable onDeselect = null;
            private Runnable onSingleTapUp = null;
            private PopupMenu.Option[] options = null;
            private boolean resizeToAccomodate;

            public Builder(String title) {
                this.title = title;
                config = MENU_TITLE_STYLE;
            }

            public Builder config(StringTexture.Config config) {
                this.config = config;
                return this;
            }

            public Builder resizeToAccomodate() {
                this.resizeToAccomodate = true;
                return this;
            }

            public Builder icon(int icon) {
                this.icon = icon;
                return this;
            }

            public Builder onSelect(Runnable onSelect) {
                this.onSelect = onSelect;
                return this;
            }

            public Builder onDeselect(Runnable onDeselect) {
                this.onDeselect = onDeselect;
                return this;
            }

            public Builder onSingleTapUp(Runnable onSingleTapUp) {
                this.onSingleTapUp = onSingleTapUp;
                return this;
            }
            
            public Builder options(PopupMenu.Option[] options) {
                this.options = options;
                return this;
            }

            public Menu build() {
                return new Menu(this);
            }
        }

        private Menu(Builder builder) {
            config = builder.config;
            title = builder.title;
            icon = builder.icon;
            onSelect = builder.onSelect;
            onDeselect = builder.onDeselect;
            onSingleTapUp = builder.onSingleTapUp;
            options = builder.options;
            resizeToAccomodate = builder.resizeToAccomodate;
        }

        public int computeRequiredWidth() {
            int width = 0;
            if (icon != 0) {
                width += (ICON_WIDTH);
            }
            if (title != null) {
                width += StringTexture.computeTextWidthForConfig(title, config);
            }
            // pad it
            width += 20;
            if (width < HEIGHT)
                width = HEIGHT;
            return width;
        }
    }
}
